/**
 * 20220112：从普通拖动组件升级为同时支持普通模式和XR模式的拖动
 * 20220304:[require] to [import]...jordia
 * 20220607:添加config时为config设置相机
 */
import Raycast from "../general/raycast";
import {EventDispatcher,Vector2} from 'three';
import Utils from '../general/utils';

const COMPARE_CLOSE=(a,b)=>{return a>b};

/**
 * 拖动
 * @param eventSourceType {string} 事件源类型（主要分为普通的crossEvent，和XR的xrEvent）
 * @param startEventSource {CrossEvent|XREvent} 起始事件源
 * @param startEvents {[string]} 起始事件
 * @param moveEventSource {CrossEvent|XREvent} 移动事件源
 * @param moveEvents {[string]} 移动事件
 * @param endEventSource{CrossEvent|XREvent} 结束事件源
 * @param endEvents {[string]} 结束事件
 * @param values {object} 可选参数对象：
 *      {
 *           enabled:true, 是否可用
 *           closestRange:0.01, 最近筛选的范围（距离相差在这一范围内即视为与参考对象处于同一距离）
 *           onlyClosest:false, 仅筛选距离最近的目标(按渲染顺序排序)
 *           shiftFrontBack:true, 切换前后（拖动）
 *      }
 *
 * 事件：
 *   dragStart,
 *   dragIn,
 *   dragEnd
 *   示例：
 *      let callback=(e)=>{};
 *      drag.eventCenter.addEventListener('dragStart',callback);
 *
 * 添加配置后才有效，如添加了dragMeshConfig后就可以添加并拖动网格对象
 * 拖动配置需要指定约束类型，比如，一般的dragFreeLimit...
 *
 * 示例：
 let wEvent=DOMCrossPlatformEvent.createEvent(window);
 wEvent.start();
 let drag=new Drag(Raycast.eventSourceTypes.crossEvent,//...............普通的
     crossEvent,['leftPointDown'],
     wEvent,['leftPointMove'],
     wEvent,['leftPointUp'],
     {onlyClosest:false});

 let dragLeft=new Drag(Raycast.eventSourceTypes.xrEvent,//...............VR左手拖动
    xrEvent,['leftPointDown'],
    xrEvent,['leftPointMove'],
    xrEvent,['leftPointUp'],
    {onlyClosest:false});
 let dragRight=new Drag(Raycast.eventSourceTypes.xrEvent,//...............VR右手拖动
    xrEvent,['rightPointDown'],
    xrEvent,['rightPointMove'],
    xrEvent,['rightPointUp'],
    {onlyClosest:false});

 let dragConfig=new DragMeshConfig({});//............................添加配置及约束
 dragConfig.limit=new DragFreeLimit({});
 drag.camera=camCtr.camera;//普通模式必须
 drag.rect=camCtr.rect;//普通模式必须
 drag.addConfig(dragConfig);//普通模式需要在设置完相机后再添加配置

 let br = new Breaker();//.......................................设置中断器（视具体需求）
 br.get = () => {
        return camCtr.enabled;
    };
 br.set = (value) => {
        camCtr.enabled = value;
    };
 br.inValue = false;
 br.outValue = true;
 br.enforce = true;
 drag.addBreaker(br);

 drag.add(...objects);//.........................................添加可拖动物体

 */
export default class Drag{
    constructor(eventSourceType,
                startEventSource,startEvents,
                moveEventSource,moveEvents,
                endEventSource,endEvents,
                values={}){
        this.type='Drag';
        this._raycastStart=new Raycast(startEventSource,eventSourceType);
        this._raycastStart.events=startEvents;
        this._raycastMove=new Raycast(moveEventSource,eventSourceType);
        this._raycastMove.events=moveEvents;
        this._raycastEnd=new Raycast(endEventSource,eventSourceType);
        this._raycastEnd.events=endEvents;
        this._eventCenter=new EventDispatcher();
        this._init(values);
    }

    //-------------------------------对外属性及方法------------------------------------

    /**
     * 获取开始射线更新器
     * @returns {Raycast}
     */
    get raycastStart(){
        return this._raycastStart;
    }

    /**
     * 获取过程射线更新器
     * @returns {Raycast}
     */
    get raycastMove(){
        return this._raycastMove;
    }

    /**
     * 获取结束射线更新器
     * @returns {Raycast}
     */
    get raycastEnd(){
        return this._raycastEnd;
    }

    /**
     * 事件对象
     * @returns {EventDispatcher}
     */
    get eventCenter(){
        return this._eventCenter;
    }

    set camera(camera){
        this._raycastStart.camera=camera;
        this._raycastMove.camera=camera;
        this._raycastEnd.camera=camera;
    }

    /**
     * 设置rect（窗体resize时需更新rect）
     * @param rect
     */
    set rect(rect){
        this._raycastStart.rect=rect;
        this._raycastMove.rect=rect;
        this._raycastEnd.rect=rect;
    }

    /**
     * 设置拖动对象的类型优先顺序（拾取距离相同时影响）
     * @param classes
     */
    set order(classes){
        this._orders=classes;
    }

    /**
     * 获取类型优先顺序
     * @returns {*|null|[]}
     */
    get order(){
        return this._orders;
    }

    /**
     * 获取可拖动对象
     * @returns {[]|null}
     */
    get targets(){
        return this._objects;
    }

    /**
     * 释放未完成的动作
     */
    releaseUnfinishedEvent(){
        this._onPointerUp();
    }

    /**
     * 添加拖动配置
     * @param configs
     */
    addConfig(...configs){
        for(let config of configs){
            let idx=-1;
            for(let i=0;i<this._config.length;i++){
                if(this._config[i].type===config.type){
                    idx=i;
                    break;
                }
            }
            if(idx>-1)continue;
            config.setCamera(this.raycastStart.camera);
            this._config.push(config);
        }
    }

    /**
     * 移除拖动配置
     * @param configs
     */
    removeConfig(...configs){
        for(let config of configs){
            let idx=-1;
            for(let i=0;i<this._config.length;i++){
                if(this._config[i].type===config.type){
                    idx=i;
                    break;
                }
            }
            if(idx<0)continue;
            this._config.splice(idx,1);
        }
    }

    /**
     * 清除拖动配置
     */
    clearConfig(){
        this._config=[];
    }

    /**
     * 添加可拖动对象
     * @param objects
     */
    add(...objects) {
        for(let object of objects) {
            let config=this._getConfig(object);
            if(!config)continue;
            let idx=this._getObjectIndex(object,config);
            if(idx>-1)continue;
            this._objects.push(object);
        }
    }

    /**
     * 移除可拖动对象
     * @param objects
     */
    remove(...objects) {
        for(let object of objects) {
            let config=this._getConfig(object);
            if(!config)continue;
            let idx=this._getObjectIndex(object,config);
            if(idx<0)continue;
            this._objects.splice(idx,1);
        }
    }

    /**
     * 清除可拖动对象
     */
    clear() {
        this._objects = [];
    }

    /**
     * 添加中断对象
     * @param breakers
     */
    addBreaker(...breakers){
        for(let br of breakers){
            let has=false;
            for(let b of this._breaker){
                if(b.uuid===br.uuid){
                    has=true;
                    break;
                }
            }
            if(!has)this._breaker.push(br);
        }
    }

    /**
     * 移除中断对象
     * @param breakers
     */
    removeBreaker(...breakers){
        for(let br of breakers){
            for(let i=0;i<this._breaker.length;i++){
                if(this._breaker[i].uuid===br.uuid){
                    this._breaker.splice(i,1);
                    break;
                }
            }
        }
    }

    /**
     * 清除中断对象
     */
    clearBreaker(){
        this._breaker=[];
    }

    /**
     * 销毁
     */
    dispose() {
        this._removeEventListener();
        this._config.length=0;
        this._breaker.length=0;
        this._objects.length=0;
        this._orders.length=0;
        this._tA0.length=0;
        this._tA1.length=0;
    }

    /**
     * 是否可用
     */
    get enabled(){
        return this._enabled;
    }
    set enabled(bol){
        if(this._enabled===bol)return;
        this._enabled=bol;
        this._enabled?this._addEventListener():this._removeEventListener();
    }

    /**
     * 最小距离值
     */
    get closestRange(){
        return this._closestRange;
    }
    set closestRange(number){
        this._closestRange=number;
    }

    /**
     * 始终为最近目标
     */
    get onlyClosest(){
        return this._onlyClosest;
    }
    set onlyClosest(bol){
        this._onlyClosest=bol;
    }

    /**
     * 选取时前后依次
     */
    get shiftFrontBack(){
        return this._shiftFrontBack;
    }
    set shiftFrontBack(bol){
        this._shiftFrontBack=bol;
    }

    //----------------------------------------------私有属性及方法------------------------------------------------

    _getConfig(object){
        for(let c of this._config){
            if(c.checkType(object)){
                return c;
            }
        }
        return null;
    }
    _getObjectIndex(object,config){
        for(let i=0;i<this._objects.length;i++){
            let ob=this._objects[i];
            if(config.checkType(ob)){
                if(config.compare(object,ob)){
                    return i;
                }
            }
        }
        return -1;
    }

    _init(values){
        this._initVariable(values);
        this._initEventBind();
    }
    _initEventBind(){
        this._fireStartEvent=(obj)=>{
            this._dragStartEvent.paras.target=obj;
            this._dragStartEvent.paras.i=this._pointerIntersect;
            this._eventCenter.dispatchEvent(this._dragStartEvent);
        };
        this._fireDragEvent=(obj,t)=>{
            this._dragInEvent.paras.target=obj;
            this._dragInEvent.paras.t=t;
            this._dragInEvent.paras.p=this._movePointer;
            this._eventCenter.dispatchEvent(this._dragInEvent);
        };
        this._fireEndEvent=(obj)=>{
            this._dragEndEvent.paras.target=obj;
            this._eventCenter.dispatchEvent(this._dragEndEvent);
        };
        this._onPointerDownBind = (e) => {
            this._onPointerDown(e);
        };
        this._onPointerMoveBind = (e) => {
            this._onPointerMove(e);
        };
        this._onPointerUpBind = (e) => {
            this._onPointerUp(e);
        };
        if(this._enabled)this._addEventListener();
    }
    _addEventListener() {
        for(let eventType of this._raycastStart.events){
            this._raycastStart.eventCenter.addEventListener(eventType, this._onPointerDownBind);
        }
        for(let eventType of this._raycastMove.events){
            this._raycastMove.eventCenter.addEventListener(eventType, this._onPointerMoveBind);
        }
        for(let eventType of this._raycastEnd.events){
            this._raycastEnd.eventCenter.addEventListener(eventType, this._onPointerUpBind);
        }
    }
    _removeEventListener(){
        for(let eventType of this._raycastStart.events){
            this._raycastStart.eventCenter.removeEventListener(eventType, this._onPointerDownBind);
        }
        for(let eventType of this._raycastMove.events){
            this._raycastMove.eventCenter.removeEventListener(eventType, this._onPointerMoveBind);
        }
        for(let eventType of this._raycastEnd.events){
            this._raycastEnd.eventCenter.removeEventListener(eventType, this._onPointerUpBind);
        }
    }
    _initVariable(values){
        this._config=[];
        this._objects=[];
        this._orders=[];
        this._breaker=[];
        this._dragStartEvent = {type: 'dragStart', paras: {}};
        this._dragInEvent = {type: 'dragIn', paras:{}};
        this._dragEndEvent = {type: 'dragEnd', paras: {}};
        this._tA0=[];
        this._intCount=0;
        this._tA1=[];
        this._oriPointer=new Vector2();
        this._pointerIntersect=null;
        this._movePointer=new Vector2();
        this._enabled=values.hasOwnProperty('enabled')?values.enabled:true;
        this._closestRange=values.closestRange||0.01;
        this._onlyClosest=values.onlyClosest||false;
        this._shiftFrontBack=values.hasOwnProperty('shiftFrontBack')?values.shiftFrontBack:true;
    }
    _cannotStart(){
        return (
            this._dragging ||
            (!this._objects.length)||
            (!this._enabled)
        );
    }
    _pointerDownAccept(){
        if(!this._enabled)return;
        for(let b of this._breaker){
            b.interrupt=true;
        }
        this._fireStartEvent(this._rayObject);
    }
    _onPointerDown() {
        if (this._cannotStart()) return;
        this._pointerIntersect = this._intersectObjects();
        if (this._pointerIntersect) {
            this._dragging = true;
            this._rayObject = this._pointerIntersect.object;
            let config = this._getConfig(this._rayObject);
            config.setDragStart(this._pointerIntersect, this._rayObject);
            this._pointerDownAccept();
        }
    }
    _onPointerMove() {
        if (!this._dragging)return;
        let config=this._getConfig(this._rayObject);
        config.setDragMove(this._rayObject,this._raycastMove.raycaster,this._fireDragEvent);
    }
    _onPointerUp() {
        if (!this._dragging)return;
        this._dragging = false;
        this._fireEndEvent(this._rayObject);
        this._rayObject = undefined;
        this._intersectObject = undefined;
        for(let b of this._breaker){
            b.interrupt=false;
        }
    }
    _intersectObjects() {
        let intersections=[];
        for(let object of this._objects){
            let config=this._getConfig(object);
            if(!config)continue;
            let intersection=config.intersectObject(object,this._raycastStart.raycaster);
            if(Array.isArray(intersection)){
                intersections.push(...intersection);
            }else{
                intersections.push(intersection);
            }
        }
        if(intersections.length===0)return false;
        if(this._onlyClosest) {
            this._sortClosest(intersections);
        }
        let target = false;
        if (this._shiftFrontBack) {
            target=this._shiftOrder(intersections);
        } else {
            target = this._sortOrders(intersections);
        }
        return target;
    }
    _shiftOrder(intersections){
        let tar=false;
        if (intersections.length > 1) {
            this._intCount = this._intCount > intersections.length - 1 ? 0 : this._intCount;
            tar = intersections[this._intCount] || false;
            this._intCount++;
        } else {
            tar = intersections[0]||false;
            this._intCount=0;
        }
        return tar;
    }
    _sortOrders(intersections){
        let indices = [];
        for (let i = 0; i < this._orders.length; i++) {
            indices.push(null);
        }
        for (let i = 0; i < intersections.length; i++) {
            let temp = intersections[i].object;
            for (let j = 0; j < indices.length; j++) {
                if (indices[j] === null && (temp instanceof this._orders[j])) {
                    indices[j] = i;
                    if (j === 0)return intersections[i];
                }
            }
        }
        for (let i = 0; i < indices.length; i++) {
            if (indices[i] !== null)return intersections[indices[i]];
        }
        return intersections[0];
    }
    _sortClosest(intersections){
        this._tA0.length=0;
        let closest = this._tA0;
        this._tA1.length=0;
        let renderOrders = this._tA1;
        let ref =Number.POSITIVE_INFINITY;
        for(let its of intersections){
            if(its.distance<ref)ref=its.distance;
        }
        for (let i = 0; i < intersections.length; i++) {
            if (Utils.zoneEqual(ref,this._closestRange,intersections[i].distance)) {
                closest.push(intersections[i]);
                renderOrders.push(intersections[i].object.renderOrder);
            }
        }
        let sorts = Utils.sortIndexArray(renderOrders,COMPARE_CLOSE);
        intersections.length=0;
        for (let idx of sorts) {
            intersections.push(closest[idx]);
        }
    }
}